#include "Dri_WIFI.h"
/* Wi-Fi Provisioning Manager Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#define CONFIG_EXAMPLE_PROV_TRANSPORT_BLE 1

#include "Dri_WIFI.h"

static const char *TAG = "app";

//// 网络传输安全可以全部去除

/**
 * @brief 定义 Wi-Fi 连接成功的事件标志位。
 *
 * 该常量用于表示 Wi-Fi 连接成功这一事件，在 FreeRTOS 事件组中作为一个标志位使用。
 * 当 Wi-Fi 成功连接并获取到 IP 地址时，会将此标志位设置到事件组中。
 */
const int WIFI_CONNECTED_EVENT = BIT0;
/**
 * @brief 定义一个静态的 FreeRTOS 事件组句柄。
 *
 * 该事件组用于信号 Wi-Fi 相关的事件，例如 Wi-Fi 连接成功等。
 * 在整个程序运行期间，该事件组会被用于同步 Wi-Fi 连接状态。
 */
static EventGroupHandle_t wifi_event_group;
// 配网QR码版本号
#define PROV_QR_VERSION "v1"
// 使用BLE配网方式标识
#define PROV_TRANSPORT_BLE "ble"
// QR码生成基础URL
#define QRCODE_BASE_URL "https://espressif.github.io/esp-jumpstart/qrcode.html"

static Dri_WIFI_OK s_wifi_ok;

/**
 * @brief 系统事件处理函数
 *
 * 处理Wi-Fi配网、连接、IP获取等各类系统事件
 *
 * @param arg 用户自定义参数(未使用)
 * @param event_base 事件基类型
 * @param event_id 具体事件ID
 * @param event_data 事件相关数据
 */
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {

  static int retries;

  /////////////////    处理Wi-Fi配网事件
  if (event_base == WIFI_PROV_EVENT) {
    switch (event_id) {
      // 配网开始事件
      case WIFI_PROV_START:
        MY_LOGE("配网开始事件");
        ESP_LOGI(TAG, "Provisioning started");
        break;

      // 接收到Wi-Fi凭证事件
      case WIFI_PROV_CRED_RECV:
        wifi_sta_config_t *wifi_sta_cfg = (wifi_sta_config_t *)event_data;
        MY_LOGE("接收到Wi-Fi凭证事件");
        ESP_LOGI(TAG,
                 "Received Wi-Fi credentials"
                 "\n\tSSID     : %s\n\tPassword : %s",
                 (const char *)wifi_sta_cfg->ssid, (const char *)wifi_sta_cfg->password);
        break;

      // 配网失败事件
      case WIFI_PROV_CRED_FAIL:
        wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *)event_data;
        MY_LOGE("配网失败事件");
        ESP_LOGE(TAG,
                 "Provisioning failed!\n\tReason : %s"
                 "\n\tPlease reset to factory and retry provisioning",
                 (*reason == WIFI_PROV_STA_AUTH_ERROR) ? "Wi-Fi station authentication failed" : "Wi-Fi access-point not found");

        while (1) {
          retries++;
          if (retries >= 5) {
            ESP_LOGI(TAG, "Failed to connect with provisioned AP, reseting "
                          "provisioned credentials");
            // 重置配网状态机，清除已保存的凭证
            wifi_prov_mgr_reset_sm_state_on_failure();
            retries = 0;
            break;
          }
        }
        break;

      // 配网成功事件
      case WIFI_PROV_CRED_SUCCESS:
        MY_LOGE("配网成功事件");
        ESP_LOGI(TAG, "Provisioning successful");
        // 重置
        retries = 0;
        break;

      // 配网结束事件
      case WIFI_PROV_END:
        MY_LOGE("配网结束事件");
        wifi_prov_mgr_deinit();
        break;
      default:
        break;
    }
  }

  /////////////////   ESP32 是客户端模式  处理Wi-Fi连接事件
  if (event_base == WIFI_EVENT) {
    switch (event_id) {
      // Wi-Fi STA模式启动事件
      case WIFI_EVENT_STA_START:
        MY_LOGE("Wi-Fi STA模式启动事件");
        esp_wifi_connect();
        break;

      // STA模式断开连接事件
      case WIFI_EVENT_STA_DISCONNECTED:
        MY_LOGE("STA模式断开连接事件");
        ESP_LOGI(TAG, "Disconnected. Connecting to the AP again...");
        retries++;
        if (retries >= 10) {
          // 删除上一次配网信息
          wifi_prov_mgr_reset_provisioning();
          // 重启
          esp_restart();
        }
        esp_wifi_connect();
        break;

      // SoftAP 模式下，有客户端连接到设备的 Wi-Fi 热点事件
      case WIFI_EVENT_AP_STACONNECTED:
        MY_LOGE("SoftAP 模式下，有客户端连接到设备的 Wi-Fi 热点事件");
        ESP_LOGI(TAG, "SoftAP transport: Connected!");
        break;

      // SoftAP 模式下，有客户端从设备的 Wi-Fi 热点断开连接事件
      case WIFI_EVENT_AP_STADISCONNECTED:
        MY_LOGE("SoftAP 模式下，有客户端从设备的 Wi-Fi 热点断开连接事件");
        ESP_LOGI(TAG, "SoftAP transport: Disconnected!");
        break;

      default:
        break;
    }
  }

  // ///////////////    处理IP获取事件
  if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {

    ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
    MY_LOGE("处理IP获取事件");
    ESP_LOGI(TAG, "Connected with IP Address:" IPSTR, IP2STR(&event->ip_info.ip));

    // 该标志位用于通知其他任务 Wi-Fi 已经成功连接并获取到 IP 地址
    xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_EVENT);

    // 成功获取IP地址,说明连接成功,设置回调函数
    s_wifi_ok();
  }

  // ///////////////    处理传输层事件
  if (event_base == PROTOCOMM_TRANSPORT_BLE_EVENT) {
    // 蓝牙传输连接事件
    switch (event_id) {

      // 当设备通过蓝牙传输成功连接时，触发此事件。
      case PROTOCOMM_TRANSPORT_BLE_CONNECTED:
        MY_LOGE("蓝牙传输成功连接时");
        ESP_LOGI(TAG, "BLE transport: Connected!");
        break;

      // 当设备通过蓝牙传输断开连接时，触发此事件。
      case PROTOCOMM_TRANSPORT_BLE_DISCONNECTED:
        MY_LOGE("蓝牙传输断开连接时");
        ESP_LOGI(TAG, "BLE transport: Disconnected!");
        break;
      default:
        break;
    }
  }

  // ///////////////    处理安全层事件
  if (event_base == PROTOCOMM_SECURITY_SESSION_EVENT) {
    switch (event_id) {
      case PROTOCOMM_SECURITY_SESSION_SETUP_OK:
        ESP_LOGI(TAG, "Secured session established!");
        break;
      case PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS:
        ESP_LOGE(TAG, "Received invalid security parameters for establishing secure "
                      "session!");
        break;
      case PROTOCOMM_SECURITY_SESSION_CREDENTIALS_MISMATCH:
        ESP_LOGE(TAG, "Received incorrect username and/or PoP for establishing "
                      "secure session!");
        break;
      default:
        break;
    }
  }
}

/**
 * @brief 初始化 Wi-Fi 为 STA（Station）模式
 *
 * 该函数的主要作用是将 ESP32 的 Wi-Fi 模式设置为 STA 模式，
 * 并启动 Wi-Fi 功能，使设备能够作为客户端连接到现有的 Wi-Fi 接入点。
 *
 * 在调用该函数之前，需要确保已经完成了 Wi-Fi 相关的初始化配置，
 * 如 TCP/IP 协议栈的初始化、事件循环的创建等。
 *
 * @return 无
 */
static void wifi_init_sta(void) {
  /* Start Wi-Fi in station mode */
  ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
  ESP_ERROR_CHECK(esp_wifi_start());
}

/**
 * @brief 生成设备服务名称
 *
 * 该函数根据设备的 Wi-Fi STA 模式的 MAC 地址生成一个唯一的设备服务名称。
 * 服务名称由固定前缀 "PROV_" 和 MAC 地址的后三个字节组成，以十六进制格式表示。
 * 生成的服务名称可用于 Wi-Fi 配网过程中的 SSID 或者 BLE 设备名称。
 *
 * @param service_name 用于存储生成的设备服务名称的字符数组指针。
 * @param max 字符数组 `service_name` 的最大长度，用于防止缓冲区溢出。
 */
static void Dri_get_device_service_name(char *service_name, size_t max) {
  uint8_t eth_mac[6];
  const char *ssid_prefix = "PROV_YHF_2_";
  esp_wifi_get_mac(WIFI_IF_STA, eth_mac);
  snprintf(service_name, max, "%s%02X%02X%02X", ssid_prefix, eth_mac[3], eth_mac[4], eth_mac[5]);
}

/**
 * @brief 处理自定义配网端点的数据
 *
 * 此函数作为自定义配网端点的处理函数，在配网过程中接收到对应端点的数据时被调用。
 * 它会记录接收到的数据，并返回一个表示成功的响应信息。
 *
 * @param session_id 配网会话的唯一标识符，用于区分不同的配网会话。
 * @param inbuf 指向输入数据缓冲区的指针，包含从客户端接收到的数据。
 * @param inlen 输入数据的长度，以字节为单位。
 * @param outbuf 指向输出数据缓冲区指针的指针，用于存储要返回给客户端的响应数据。
 * @param outlen 指向输出数据长度的指针，用于存储响应数据的长度。
 * @param priv_data 指向私有数据的指针，可用于传递自定义的上下文信息，在本函数中未使用。
 *
 * @return esp_err_t 操作结果状态码：
 *         - ESP_OK: 操作成功，成功分配内存并设置响应数据。
 *         - ESP_ERR_NO_MEM: 内存分配失败，无法为响应数据分配足够的内存。
 */
esp_err_t custom_prov_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data) {
  if (inbuf) {
    ESP_LOGI(TAG, "Received data: %.*s", inlen, (char *)inbuf);
  }
  char response[] = "SUCCESS";
  *outbuf = (uint8_t *)strdup(response);
  if (*outbuf == NULL) {
    ESP_LOGE(TAG, "System out of memory");
    return ESP_ERR_NO_MEM;
  }
  *outlen = strlen(response) + 1; /* +1 for NULL terminating byte */

  return ESP_OK;
}

/**
 * @brief 生成并打印配网二维码相关信息
 *
 * 该函数根据输入的设备名称、用户名、证明所有权（PoP）和传输方式，
 * 生成配网所需的二维码数据，并根据配置决定是否生成二维码图形，
 * 同时打印出可在浏览器中使用的二维码数据链接。
 *
 * @param name 设备服务名称，用于标识设备，在配网过程中会显示在二维码中。
 * @param username 用户名，在安全版本 2 中可能会用到，可为 NULL。
 * @param pop 证明所有权（Proof of Possession），用于安全认证，可为 NULL。
 * @param transport 配网传输方式，如 "ble" 或 "softap"。
 */
static void Dri_wifi_prov_print_qr(const char *name, const char *username, const char *pop, const char *transport) {
  if (!name || !transport) {
    ESP_LOGW(TAG, "Cannot generate QR code payload. Data missing.");
    return;
  }
  char payload[150] = {0};

  // 若未提供 PoP，根据设备名称和传输方式生成二维码数据
  snprintf(payload, sizeof(payload),
           "{\"ver\":\"%s\",\"name\":\"%s\""
           ",\"transport\":\"%s\"}",
           PROV_QR_VERSION, name, transport);

  // 若配置允许显示二维码，记录日志提示用户扫描二维码进行配网
  esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG_DEFAULT();
  esp_qrcode_generate(&cfg, payload);

  MY_LOGE("生成并打印配网二维码相关信息");
  ESP_LOGI(TAG,
           "If QR code is not visible, copy paste the below URL in a "
           "browser.\n%s?data=%s",
           QRCODE_BASE_URL, payload);
}

/**
 * @brief 应用程序的入口函数
 *
 * 此函数负责初始化系统所需的各项组件，包括非易失性存储（NVS）分区、TCP/IP 协议栈、事件循环等。
 * 同时，会注册各类事件的处理函数，并根据配置初始化 Wi-Fi 和配网管理器。
 * 最后，根据设备的配网状态决定是启动配网流程还是直接启动 Wi-Fi 客户端模式。
 */
void Dri_WIFI_Init(Dri_WIFI_OK wifi_ok) {
  // 删除所有的配网信息
  wifi_prov_mgr_reset_provisioning();
  vTaskDelay(1000);
  MY_LOGE(" 删除所有的配网信息");

  /* 初始化 NVS 分区，NVS 用于存储设备的非易失性数据，如 Wi-Fi 凭证等 */
  esp_err_t ret = nvs_flash_init();
  if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    /* NVS partition was truncated
     * and needs to be erased */
    ESP_ERROR_CHECK(nvs_flash_erase());

    /* Retry nvs_flash_init */
    ESP_ERROR_CHECK(nvs_flash_init());
    MY_LOGE("NVS 分区被截断或版本不一致,失败");
  }

  /* 初始化 TCP/IP 协议栈，为网络通信提供基础 */
  ESP_ERROR_CHECK(esp_netif_init());
  MY_LOGE("初始化 TCP/IP 协议栈");

  /* 初始化默认的事件循环，用于处理各类系统事件 */
  ESP_ERROR_CHECK(esp_event_loop_create_default());
  MY_LOGE("初始化默认的事件循环");

  MY_LOGE("创建一个 FreeRTOS 事件组，用于同步 Wi-Fi 相关的事件");
  wifi_event_group = xEventGroupCreate();

  /* Register our event handler for Wi-Fi, IP and Provisioning related events */
  ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
  ESP_ERROR_CHECK(esp_event_handler_register(PROTOCOMM_TRANSPORT_BLE_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
  ESP_ERROR_CHECK(esp_event_handler_register(PROTOCOMM_SECURITY_SESSION_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
  ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
  ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
  MY_LOGE("注册事件处理函数-- 注册 Wi-Fi  结束");

  esp_netif_create_default_wifi_sta();
  wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  ret = esp_wifi_init(&cfg);
  MY_LOGE("初始化 Wi-Fi  结束 %d", ret);

  // 配置 Wi-Fi 配网管理器
  wifi_prov_mgr_config_t config = {
      .scheme = wifi_prov_scheme_ble,
      .scheme_event_handler = WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM,
  };

  // 初始化 Wi-Fi 配网管理器
  ret = wifi_prov_mgr_init(config);
  MY_LOGE("初始化 Wi-Fi 配网管理器  结束 %d", ret);

  // 用于标记设备是否已经完成配网
  bool provisioned = false;
  /* 检查设备是否已经完成配网 */
  esp_err_t err = wifi_prov_mgr_is_provisioned(&provisioned);
  MY_LOGE("检查设备是否已经完成配网-- %d", err);

  /* 如果设备尚未完成配网，则启动配网服务 */
  if (!provisioned) {
    ESP_LOGI(TAG, "Starting provisioning");

    /* 生成设备服务名称
     * 该名称在不同配网方案中有不同含义：
     *     - 当配网方案为 wifi_prov_scheme_softap 时，代表 Wi-Fi SSID
     *     - 当配网方案为 wifi_prov_scheme_ble 时，代表设备名称
     */
    char service_name[12];
    Dri_get_device_service_name(service_name, sizeof(service_name));

    //// 安全等级去除

    // 设置服务密钥 (可以为 NULL)
    const char *service_key = NULL;

    /* 此步骤仅在配网方案为 wifi_prov_scheme_ble 时有用。
     * 它会设置一个自定义的 128 位 UUID，该 UUID 将包含在 BLE 广播中，
     * 并对应于提供配网端点的主 GATT 服务。
     * 每个 GATT 特征将使用主服务 UUID 作为基础，自动分配第 12 和 13 个字节 (假设从第 0 个字节开始计数)。
     * 客户端应用程序必须通过读取每个特征的用户特征描述描述符 (0x2901) 来识别端点，
     * 该描述符包含特征的端点名称。
     */
    uint8_t custom_service_uuid[] = {
        0xb4, 0xdf, 0x5a, 0x1c, 0x3f, 0x6b, 0xf4, 0xbf, 0xea, 0x4a, 0x82, 0x03, 0x04, 0x90, 0x1a, 0x02,
    };
    wifi_prov_scheme_ble_set_service_uuid(custom_service_uuid);

    // 应用程序可以创建一个可选的端点，用于在配网流程中获取一些额外的自定义数据。
    wifi_prov_mgr_endpoint_create("custom-data");

    /* 启动配网服务 */
    err = wifi_prov_mgr_start_provisioning(WIFI_PROV_SECURITY_0, NULL, service_name, service_key);
    MY_LOGE("检查设备是否已经完成配网-- %d", err);

    /* 注册上面创建的可选端点的处理函数。
     * 此调用必须在启动配网之后进行，并且仅当上面已经创建了端点时才需要调用。
     */
    wifi_prov_mgr_endpoint_register("custom-data", custom_prov_data_handler, NULL);

    // 生成并打印配网二维码相关信息
    Dri_wifi_prov_print_qr(service_name, NULL, NULL, PROV_TRANSPORT_BLE);

  } else {
    ESP_LOGI(TAG, "Already provisioned, starting Wi-Fi STA");

    /* We don't need the manager as device is already provisioned,
     * so let's release it's resources */
    wifi_prov_mgr_deinit();

    /* Start Wi-Fi station */
    wifi_init_sta();
  }

  // 回调函数赋值,启动其他业务
  s_wifi_ok = wifi_ok;
}